﻿using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace NetSync.Core
{
    public class MemoryWaitSync : IWaitSync
    {
        private readonly ConcurrentDictionary<string, (TaskCompletionSource<object>, DateTime)> _taskDic;
        //private readonly Task _task;
        //private readonly CancellationTokenSource _cts;

        public MemoryWaitSync()
        {
            _taskDic = new ConcurrentDictionary<string, (TaskCompletionSource<object>, DateTime)>();
            //_cts = new CancellationTokenSource();
            //_task = Task.Factory.StartNew(async (arg) =>
            //{
            //    while (!_cts.IsCancellationRequested)
            //    {
            //        var keys = _taskDic.Keys.ToArray();
            //        var count = keys.Length + (keys.Length % 2);
            //        var loop = count / 2;
            //        for (var i = 0; i < loop; i++)
            //        {
            //            SetTaskTimeout(i, loop, keys);
            //            SetTaskTimeout(loop + i, keys.Length, keys);
            //        }

            //        keys = null;
            //        await Task.Delay(200);
            //    }
            //}, null, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
        }

        /// <summary>
        /// 同步请求回复，key在内存中不存在时，此处可以单独再处理转数据到其他节点
        /// </summary>
        /// <returns>true:说明数据已经处理; false: 数据未处理</returns>
        protected virtual Task<bool> ReleaseExternalAsync<TData>(string key, TData data)
        {
            return Task.FromResult(false);
        }

        public Task<bool> ReleaseAsync<TData>(string key, TData data)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                //var e = new ArgumentNullException(nameof(key));
                //throw new WaitSyncException(e.Message, e);
                return Task.FromResult(false);
            }

            if (_taskDic.TryRemove(key, out var item))
            {
                item.Item1.TrySetResult(data);
                return Task.FromResult(true);
            }

            return ReleaseExternalAsync(key, data);
        }

        public async Task<TData> WaitAsync<TData>(string key, Func<Task> action, int timeoutSecond = 120)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                var e = new ArgumentNullException(nameof(key));
                throw new WaitSyncException(e.Message, e);
            }

            var key1 = key;
            var t = timeoutSecond <= 0 ? 120 : timeoutSecond;
            var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
            if (!_taskDic.TryAdd(key1, (tcs, DateTime.Now.AddSeconds(t))))
                throw new WaitSyncException($"{nameof(key)} not unique");
            var cts = new CancellationTokenSource(t * 1000);
            cts.Token.Register(() =>
            {
                tcs.TrySetException(new TimeoutException($"operation timeout {t} second"));
                _taskDic.TryRemove(key1, out _);
            });
            try
            {
                if (action != null)
                    await action();
            }
            catch (Exception e)
            {
                cts?.Dispose();
                _taskDic.TryRemove(key1, out _);
                throw e;
            }
            var result = await tcs.Task;
            cts?.Dispose();
            return (TData)Convert.ChangeType(result, typeof(TData));
        }

        public void Dispose()
        {
            //_cts.Cancel();
            //_task?.Dispose();
            foreach (var task in _taskDic.Values)
            {
                task.Item1.TrySetCanceled();
            }

            _taskDic.Clear();
        }

        #region private methods
        private void SetTaskTimeout(int index, int Count, string[] keys)
        {
            for (var i = index; i < Count; i++)
            {
                if (i < keys.Length)
                {
                    if (_taskDic.ContainsKey(keys[i]))
                    {
                        try
                        {
                            if (_taskDic[keys[i]].Item2 < DateTime.Now)
                            {
                                _taskDic[keys[i]].Item1.TrySetException(new TimeoutException());
                                _taskDic.TryRemove(keys[i], out _);
                            }
                        }
                        catch { }
                    }
                }
            }
        }
        #endregion
    }
}
